/* Copyright (c) 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package google.sites.model;

import com.google.gdata.client.sites.SitesService;
import com.google.gdata.data.MediaContent;
import com.google.gdata.data.XhtmlTextConstruct;
import com.google.gdata.data.acl.AclEntry;
import com.google.gdata.data.acl.AclFeed;
import com.google.gdata.data.sites.*;
import com.google.gdata.data.spreadsheet.Column;
import com.google.gdata.data.spreadsheet.Field;
import com.google.gdata.util.ServiceException;
import dev.tools.design.patterns.state.Context;
import google.sites.model.states.Aborted;
import google.sites.model.states.Created;
import google.sites.model.states.LoggedIn;

import javax.activation.MimetypesFileTypeMap;
import java.io.*;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;

/**
 * Wrapper class for lower level Sites API calls.
 */
public class Model extends Context<Model, Model.State> {
    public static final Created CREATED = new Created();
    public static final Aborted ABORTED = new Aborted();

    public static final LoggedIn LOGGED_IN = new LoggedIn();

    public String getDomain() {
        return domain;
    }

    public String getSiteName() {
        return siteName;
    }

    public static class Exception extends RuntimeException{
        public Exception(String message) {
            super(message);
        }
    }

    public static final Model INSTANCE = new Model();
    public final SitesService service = new SitesService("google-sites-manager-v0.1");
    public final MimetypesFileTypeMap mediaTypes;
    private String domain;
    private String siteName;

    private Model() {
        super(CREATED);

        File configFolder = new File("config");
        if (!configFolder.exists())
            configFolder.mkdir();

        mediaTypes = new MimetypesFileTypeMap();
        try(BufferedReader bufferedReader = new BufferedReader(new FileReader("config/MimeTypes.txt"))){
            String line;
            while ((line = bufferedReader.readLine()) != null)
                mediaTypes.addMimeTypes(line);
        } catch (IOException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            setState(ABORTED);
        }
    }

    public String getContentFeedUrl() {
        return MessageFormat.format("https://sites.google.com/feeds/content/{0}/{1}/", domain, siteName);
    }

    public String getRevisionFeedUrl() {
        return MessageFormat.format("https://sites.google.com/feeds/revision/{0}/{1}/", domain, siteName);
    }

    public String getActivityFeedUrl() {
        return MessageFormat.format("https://sites.google.com/feeds/activity/{0}/{1}/", domain, siteName);
    }

    public String getAclFeedUrl(String siteName) {
        return MessageFormat.format("https://sites.google.com/feeds/acl/site/{0}/{1}/", domain, siteName);
    }

    public String getSiteFeedUrl() {
        return MessageFormat.format("https://sites.google.com/feeds/site/{0}/", domain);
    }

    /**
     * Returns an entry's numeric ID.
     */
    private String getEntryId(BaseContentEntry<?> entry) {
        String selfLink = entry.getSelfLink().getHref();
        return selfLink.substring(selfLink.lastIndexOf("/") + 1);
    }

    /**
     * Returns an entry's numeric ID.
     */
    private String getEntryId(String selfLink) {
        return selfLink.substring(selfLink.lastIndexOf("/") + 1);
    }

    /**
     * Fetches and displays the user's site feed.
     */
    public List<SiteEntry> getSiteEntries() throws IOException, ServiceException {
        return service.getFeed(
                new URL(getSiteFeedUrl()), SiteFeed.class).getEntries();
    }

    /**
     * Fetches and displays a Site's acl feed.
     */
    public List<String> getAclFeed(String siteName) throws IOException, ServiceException {
        AclFeed aclFeed = service.getFeed(
                new URL(getAclFeedUrl(siteName)), AclFeed.class);

        List<String> strings = new ArrayList<>();
        for (AclEntry entry : aclFeed.getEntries()) {
            strings.add(entry.getScope().getValue() + " (" +
                    entry.getScope().getType() + ") : " + entry.getRole().getValue());
        }
        return strings;
    }

    /**
     * Fetches and displays the Site's activity feed.
     */
    public List<String> getActivityFeed() throws IOException, ServiceException {
        ActivityFeed activityFeed = service.getFeed(
                new URL(getActivityFeedUrl()), ActivityFeed.class);

        List<String> strings = new ArrayList<>();
        for (BaseActivityEntry<?> entry : activityFeed.getEntries())
            strings.add(entry.getSummary().getPlainText());

        return strings;
    }

    /**
     * Fetches and displays the revisions feed for an entry.
     */
    public List<String> getRevisionFeed(String contentEntryId) throws IOException, ServiceException {
        URL url = new URL(getRevisionFeedUrl() + contentEntryId);
        RevisionFeed revisionFeed = service.getFeed(url, RevisionFeed.class);
        List<String> strings = new ArrayList<>();
        for (BaseContentEntry<?> entry : revisionFeed.getEntries()) {
            strings.add(entry.getTitle().getPlainText());
            strings.add("  updated: " + entry.getUpdated().toUiString() + " by " +
                    entry.getAuthors().get(0).getEmail());
            strings.add("  revision #: " + entry.getRevision().getValue());
        }
        return strings;
    }

    /**
     * Fetches and displays entries from the content feed.
     *
     * @param kind An entry kind to fetch. For example, "webPage". If null, the
     *             entire content feed is returned.
     */
    public List<String> getSiteContents(Kind kind) throws IOException, ServiceException {
        String url = kind == Kind.all ? getContentFeedUrl() : getContentFeedUrl() + "?kind=" + kind;
        List<String> list = new ArrayList<>();
        list.add(url);
        ContentFeed contentFeed = service.getFeed(new URL(url), ContentFeed.class);

        for (WebPageEntry entry : contentFeed.getEntries(WebPageEntry.class)) {
            list.add("WebPageEntry:");
            list.add("  title: " + entry.getTitle().getPlainText());
            list.add("  id: " + getEntryId(entry));
            if (entry.getParentLink() != null) {
                list.add("  parent id: " + getEntryId(entry.getParentLink().getHref()));
            }
            list.add("  authors: " + entry.getAuthors().get(0).getEmail());
            list.add("  content: " + getContentBlob(entry));
            list.add("");
        }

        for (ListPageEntry entry : contentFeed.getEntries(ListPageEntry.class)) {
            list.add("ListPageEntry:");
            list.add("  title: " + entry.getTitle().getPlainText());
            list.add("  id: " + getEntryId(entry));
            if (entry.getParentLink() != null) {
                list.add("  parent id: " + getEntryId(entry.getParentLink().getHref()));
            }
            for (Column col : entry.getData().getColumns()) {
                list.add("  [" + col.getIndex() + "] " + col.getName() + "\t");
            }
            list.add("");
        }

        for (ListItemEntry entry : contentFeed.getEntries(ListItemEntry.class)) {
            if (entry.getParentLink() != null) {
                list.add("  parent id: " + getEntryId(entry.getParentLink().getHref()));
            }
            for (Field field : entry.getFields()) {
                list.add("  [" + field.getIndex() + "] " + field.getValue() + "\t");
            }
            list.add("\n");
        }

        for (FileCabinetPageEntry entry : contentFeed.getEntries(FileCabinetPageEntry.class)) {
            list.add("FileCabinetPageEntry:");
            list.add("  title: " + entry.getTitle().getPlainText());
            list.add("  id: " + getEntryId(entry));
            if (entry.getParentLink() != null) {
                list.add("  parent id: " + getEntryId(entry.getParentLink().getHref()));
            }
            list.add("  content: " + getContentBlob(entry));
            list.add("");
        }

        for (CommentEntry entry : contentFeed.getEntries(CommentEntry.class)) {
            list.add("CommentEntry:");
            list.add("  id: " + getEntryId(entry));
            if (entry.getParentLink() != null) {
                list.add("  parent id: " + getEntryId(entry.getParentLink().getHref()));
            }
            list.add("  in-reply-to: " + entry.getInReplyTo().toString());
            list.add("  content: " + getContentBlob(entry));
            list.add("");
        }

        for (AnnouncementsPageEntry entry : contentFeed.getEntries(AnnouncementsPageEntry.class)) {
            list.add("AnnouncementsPageEntry:");
            list.add("  title: " + entry.getTitle().getPlainText());
            list.add("  id: " + getEntryId(entry));
            if (entry.getParentLink() != null) {
                list.add("  parent id: " + getEntryId(entry.getParentLink().getHref()));
            }
            list.add("  content: " + getContentBlob(entry));
            list.add("");
        }

        for (AnnouncementEntry entry : contentFeed.getEntries(AnnouncementEntry.class)) {
            list.add("AnnouncementEntry:");
            list.add("  title: " + entry.getTitle().getPlainText());
            list.add("  id: " + getEntryId(entry));
            if (entry.getParentLink() != null) {
                list.add("  parent id: " + getEntryId(entry.getParentLink().getHref()));
            }
            list.add("  draft?: " + entry.isDraft());
            list.add("  content: " + getContentBlob(entry));
            list.add("");
        }

        for (AttachmentEntry entry : contentFeed.getEntries(AttachmentEntry.class)) {
            list.add("AttachmentEntry:");
            list.add("  title: " + entry.getTitle().getPlainText());
            list.add("  id: " + getEntryId(entry));
            if (entry.getParentLink() != null) {
                list.add("  parent id: " + getEntryId(entry.getParentLink().getHref()));
            }
            if (entry.getSummary() != null) {
                list.add("  description: " + entry.getSummary().getPlainText());
            }
            list.add("  revision: " + entry.getRevision().getValue());
            MediaContent content = (MediaContent) entry.getContent();
            list.add("  src: " + content.getUri());
            list.add("  content type: " + content.getMimeType().getMediaType());
            list.add("");
        }

        for (WebAttachmentEntry entry : contentFeed.getEntries(WebAttachmentEntry.class)) {
            list.add("WebAttachmentEntry:");
            list.add("  title: " + entry.getTitle().getPlainText());
            list.add("  id: " + getEntryId(entry));
            if (entry.getParentLink() != null) {
                list.add("  parent id: " + getEntryId(entry.getParentLink().getHref()));
            }
            if (entry.getSummary() != null) {
                list.add("  description: " + entry.getSummary().getPlainText());
            }
            list.add("  src: " + ((MediaContent) entry.getContent()).getUri());
            list.add("");
        }

        return list;
    }

    public String getContentBlob(BaseContentEntry<?> entry) {
        return ((XhtmlTextConstruct) entry.getTextContent().getContent()).getXhtml().getBlob();
    }

    public Model setDomain(String domain) {
        this.domain = domain;

        return this;
    }

    public Model setSiteName(String siteName) {
        this.siteName = siteName;
        return this;
    }

    public static class State extends dev.tools.design.patterns.state.State<Model, State> {
    }
}
